/**
 * This file is part of the educational usermanagement system.
 * 
 * Copyright (c) 2008, Florian Arndt, Thomas Kemptner, Christian Kerl, Patrick Neuberger, Marcel Rehfeld, Emal Sadran, Stefan Sauer
 * 
 * Released under the MIT License http://www.opensource.org/licenses/mit-license.php
 */

package edu.ba.management.data.search;

import java.util.*;
import java.lang.reflect.*;

import edu.ba.management.data.search.comparer.*;

/**
 * The FilterChain class allows the filtering of object collections on multiple
 * fields and associated reference values.
 */
public final class FilterChain
{
    private HashMap<String, Object> filters = new HashMap<String, Object>();

    /**
     * Sets a field and a reference object to use for filtering.
     * 
     * @param field the name of the class field
     * @param value the reference value
     * @return the FilterChain object
     */
    public FilterChain add(String field, Object value)
    {
        if (value == null)
        {
            throw new java.lang.NullPointerException(
                    "Parameter value can not be null.");
        }

        this.filters.put(field, value);

        return this;
    }

    /**
     * Removes a field from the filter chain.
     * 
     * @param field the field to be removed
     * @return the FilterChain object
     */
    public FilterChain remove(String field)
    {
        this.filters.remove(field);

        return this;
    }

    /**
     * Filters a collection of objects using the registered field and reference
     * value pairs.
     * 
     * @param <T> the type of the objects in the collection
     * @param collection the collection to filter
     * @return the filtered list
     * 
     * @throws NoSuchFieldException if a registered field does not exist in the
     *             class of the collection's objects
     */
    public <T> List<T> execute(Collection<T> collection)
            throws NoSuchFieldException
    {
        List<T> result = new ArrayList<T>(collection);

        for (Map.Entry<String, Object> entry : this.filters.entrySet())
        {
            try
            {
                result = this.filter(result, entry.getKey(), entry.getValue());
            }
            catch (NullPointerException e)
            {
            }
        }

        return result;
    }

    /**
     * Filters a list of objects by comparing the values of the given field to
     * the given reference value using the IEqualityComparer for the field type.
     * 
     * @param <T> the type of the objects in the list
     * @param source a list of objects to filter
     * @param field the field to filter on
     * @param value the reference value
     * @return the filtered list
     * 
     * @throws NoSuchFieldException if the field does not exist in the class of
     *             the list's objects
     * @throws NullPointerException if the value is null
     */
    @SuppressWarnings("unchecked")
    private <T> List<T> filter(List<T> source, String field, Object value)
            throws NoSuchFieldException, NullPointerException
    {
        List<T> result = new Vector<T>();

        if (source.size() > 0)
        {
            try
            {
                Method getter = source.get(0).getClass().getDeclaredMethod(
                        getGetterName(field));
                IEqualityComparer comparer = EqualityComparerFactory
                        .getComparer(getter.getReturnType());

                for (T user : source)
                {
                    if (comparer.compare(getter.invoke(user), value))
                    {
                        result.add(user);
                    }
                }
            }
            catch (NullPointerException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw new NoSuchFieldException("Field " + field
                        + " does not exist.");
            }
        }

        return result;
    }

    /**
     * Builds the getter method name for a given field name.
     * 
     * @param field the field name
     * @return the getter name for the field
     */
    private String getGetterName(String field)
    {
        return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
    }
}
